/**
 * Copyright (c) 2011 - 2012 National Library of Medicine
 *
 * Contact: tomasz.adamusiak@nih.gov
 * 
 * This file is part of LOwlINC
 * 
 * LOwlINC is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 3 of the License, or (at your option) any
 * later version.
 * 
 * LOwlINC is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along
 * with LOwlINC. If not, see <http://www.gnu.org/licenses/>.
 */
package gov.nih.nlm.lowlinc.owlapi;

import gov.nih.nlm.lowlinc.accessionwrapper.LOINCID;
import gov.nih.nlm.lowlinc.accessionwrapper.LOINCPartID;
import gov.nih.nlm.lowlinc.accessionwrapper.LOINCTermID;
import gov.nih.nlm.lowlinc.accessionwrapper.SNOMEDTermID;
import gov.nih.nlm.lowlinc.bean.LOINCPart;
import gov.nih.nlm.lowlinc.bean.LOINCSystem2Part;
import gov.nih.nlm.lowlinc.bean.LOINCSystem3Part;
import gov.nih.nlm.lowlinc.core.LowlincConfig;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.semanticweb.owlapi.MakePrimitiveSubClassesMutuallyDisjoint;
import org.semanticweb.owlapi.OWLCompositeOntologyChange;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.io.OWLFunctionalSyntaxOntologyFormat;
import org.semanticweb.owlapi.model.AddAxiom;
import org.semanticweb.owlapi.model.AddOntologyAnnotation;
import org.semanticweb.owlapi.model.AxiomType;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAnnotation;
import org.semanticweb.owlapi.model.OWLAnnotationAssertionAxiom;
import org.semanticweb.owlapi.model.OWLAnnotationProperty;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLDataProperty;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyChange;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLOntologyStorageException;
import org.semanticweb.owlapi.model.RemoveAxiom;
import org.semanticweb.owlapi.vocab.OWLRDFVocabulary;

/**
 * @author Tomasz Adamusiak
 */
public class Ontologies {
	/** The Constant log. */
	private static final Logger log = Logger.getLogger(Ontologies.class);

	/** The Constant manager. */
	private static OWLOntologyManager manager = OWLManager
			.createOWLOntologyManager();
	private static OWLDataFactory factory = manager.getOWLDataFactory();

	/** The ontology to be loaded. */
	private static OWLOntology ontology;

	private static IRI documentIRI = null;

	private static IRI snomedBase = null;

	public static void addAnnoation(OWLClass cls, String accession, String text) {
		IRI iri = IRI.create(documentIRI + accession);
		OWLOntologyChange change = getAddAnnotationChange(cls, iri, text);
		manager.applyChange(change);
	}

	public static void addAxialObservation(OWLClass termClass,
			List<LOINCPart> partComposition) throws LowlincException {
		Set<OWLClassExpression> equRestrictions = new HashSet<OWLClassExpression>();

		for (LOINCPart part : partComposition) {
			// TODO: classic need for recurrence
			if (part instanceof LOINCSystem3Part
					|| part instanceof LOINCSystem2Part) {
				Set<OWLClassExpression> subRestrictions = new HashSet<OWLClassExpression>();
				for (LOINCPart subPart : part.getPartComposition()) {
					OWLObjectProperty property = createLOINCProperty(LOINCPart
							.getOWLRelationName(subPart.getPART_TYPE()));

					OWLClass partClass = createLOINCClass(subPart.getPartID());
					OWLClassExpression expression = factory
							.getOWLObjectSomeValuesFrom(property, partClass);

					subRestrictions.add(expression);
				}
				OWLClassExpression systemUnion = factory
						.getOWLObjectUnionOf(subRestrictions);
				equRestrictions.add(systemUnion);
			} else {
				OWLObjectProperty property = createLOINCProperty(LOINCPart
						.getOWLRelationName(part.getPART_TYPE()));

				OWLClass partClass = createLOINCClass(part.getPartID());
				OWLClassExpression expression = factory
						.getOWLObjectSomeValuesFrom(property, partClass);

				equRestrictions.add(expression);

			}
		}

		OWLClassExpression definedAxes = factory
				.getOWLObjectIntersectionOf(equRestrictions);

		addAxiom(factory.getOWLEquivalentClassesAxiom(termClass, definedAxes));
	}

	private static void addAxiom(OWLAxiom axiom) {
		AddAxiom addAxiom = new AddAxiom(ontology, axiom);
		manager.applyChange(addAxiom);
	}

	public static void addDeprecatedParent(OWLClass cls) {
		OWLClass parent = getDeprecatedParent();
		addLabel(cls, "Inactive Term");
		addParent(cls, parent);
	}

	public static void addDummyClassParents(Set<LOINCPart> classParts)
			throws LowlincException {

		Integer counter = 0;

		for (LOINCPart part : classParts) {
			OWLClass partClass = createLOINCClass(part.getPartID());

			// add dummy terms
			OWLClass dummyClassTerm = createLOINCClass(new LOINCTermID(
					"dummyClassTerm" + counter++));

			OWLObjectProperty property = createLOINCProperty(LOINCPart
					.getOWLRelationName("CLASS"));

			OWLClassExpression hasClass = factory.getOWLObjectSomeValuesFrom(
					property, partClass);
			addLabel(dummyClassTerm, part.getLabel());
			addTermsParent(dummyClassTerm);

			OWLAxiom axiom = factory.getOWLEquivalentClassesAxiom(
					dummyClassTerm, hasClass);

			addAxiom(axiom);
		}

	}

	private static void addEquivalentRelations() throws LowlincException {
		// http://www.loinc.org/loinc.owl#has_component
		//
		// Component (attribute)
		// http://www.ihtsdo.org/SCT_246093002

		OWLObjectProperty lComponent = createLOINCProperty("has_component");
		OWLObjectProperty sComponent = createSNOMEDProperty("246093002");
		if (!getLabel(sComponent).equals(sComponent.toStringID())
				&& !getLabel(sComponent).equals("Component (attribute)")) {
			throw new LowlincException("MISMATCHED SNOMED PROPERTY ID MAPPING");
		}

		OWLAxiom componentAxiom = factory
				.getOWLEquivalentObjectPropertiesAxiom(lComponent, sComponent);
		addAxiom(componentAxiom);

		// http://www.loinc.org/loinc.owl#has_scale
		//
		// Scale type (attribute)
		// http://www.ihtsdo.org/SCT_370132008

		OWLObjectProperty lScale = createLOINCProperty("has_scale");
		OWLObjectProperty sScale = createSNOMEDProperty("370132008");
		if (!getLabel(sScale).equals(sScale.toStringID())
				&& !getLabel(sScale).equals("Scale type (attribute)")) {
			throw new LowlincException("MISMATCHED SNOMED PROPERTY ID MAPPING");
		}

		OWLAxiom scaleAxiom = factory.getOWLEquivalentObjectPropertiesAxiom(
				lScale, sScale);
		addAxiom(scaleAxiom);

		// http://www.loinc.org/loinc.owl#has_time_aspect
		//
		// Time aspect (attribute)
		// http://www.ihtsdo.org/SCT_370134009

		OWLObjectProperty lTime = createLOINCProperty("has_time_aspect");
		OWLObjectProperty sTime = createSNOMEDProperty("370134009");
		if (!getLabel(sTime).equals(sTime.toStringID())
				&& !getLabel(sTime).equals("Time aspect (attribute)")) {
			throw new LowlincException("MISMATCHED SNOMED PROPERTY ID MAPPING");
		}

		OWLAxiom timeAxiom = factory.getOWLEquivalentObjectPropertiesAxiom(
				lTime, sTime);
		addAxiom(timeAxiom);

		// http://www.loinc.org/loinc.owl#has_system
		//
		// Has specimen (attribute)
		// http://www.ihtsdo.org/SCT_116686009
		// Specimen substance (attribute)
		// http://www.ihtsdo.org/SCT_370133003
		//
		// Procedure site - Direct (attribute)
		// http://www.ihtsdo.org/SCT_405813007

		OWLObjectProperty lSystem = createLOINCProperty("has_system");

		OWLObjectProperty sHasSpecimen = createSNOMEDProperty("116686009");
		if (!getLabel(sHasSpecimen).equals(sHasSpecimen.toStringID())
				&& !getLabel(sHasSpecimen).equals("Has specimen (attribute)")) {
			throw new LowlincException("MISMATCHED SNOMED PROPERTY ID MAPPING");
		}
		OWLObjectProperty sSpecimensubstance = createSNOMEDProperty("370133003");
		if (!getLabel(sSpecimensubstance).equals(
				sSpecimensubstance.toStringID())
				&& !getLabel(sSpecimensubstance).equals(
						"Specimen substance (attribute)")) {
			throw new LowlincException("MISMATCHED SNOMED PROPERTY ID MAPPING");
		}
		OWLObjectProperty sProcedureSiteDirect = createSNOMEDProperty("405813007");
		if (!getLabel(sProcedureSiteDirect).equals(
				sProcedureSiteDirect.toStringID())
				&& !getLabel(sProcedureSiteDirect).equals(
						"Procedure site - Direct (attribute)")) {
			throw new LowlincException("MISMATCHED SNOMED PROPERTY ID MAPPING");
		}

		List<OWLObjectProperty> chain = new ArrayList<>();
		chain.add(sHasSpecimen);
		chain.add(sSpecimensubstance);
		OWLAxiom chainAxiom = factory.getOWLSubPropertyChainOfAxiom(chain,
				lSystem);
		addAxiom(chainAxiom);

		OWLAxiom systemAxiom = factory.getOWLEquivalentObjectPropertiesAxiom(
				lSystem, sProcedureSiteDirect);
		addAxiom(systemAxiom);
	}

	public static void addLabel(OWLEntity ent, String text) {
		OWLOntologyChange change = getAddAnnotationChange(ent,
				OWLRDFVocabulary.RDFS_LABEL.getIRI(), text);
		manager.applyChange(change);
	}

	public static void addParent(OWLClass child, OWLClass parent) {
		OWLAxiom axiom = factory.getOWLSubClassOfAxiom(child, parent);
		AddAxiom addAxiom = new AddAxiom(ontology, axiom);

		manager.applyChange(addAxiom);

	}

	public static void addPartsParent(OWLClass cls) throws LowlincException {
		// axial information might have been added already
		// no point adding multiple inheritance that's already transitivend we
		// don't want multiple inheritance especialy transitive

		if (cls.getSuperClasses(ontology).size() == 0) {
			OWLClass parent = getPartsParent();
			addLabel(parent, LowlincConfig.getString("PARTS_ROOT_LABEL"));
			addParent(cls, parent);
		}

	}

	public static void addRelations(OWLClass termClass,
			Map<LOINCPartID, List<String>> relations, String index)
			throws LowlincException {

		Set<OWLClassExpression> equRestrictions = new HashSet<OWLClassExpression>();
		Set<OWLClassExpression> subRestrictions = new HashSet<OWLClassExpression>();

		for (LOINCPartID partID : relations.keySet()) {
			for (String relationType : relations.get(partID)) {
				if (relationType.equals("CLASS")
						|| relationType.equals("MULTI-AXIAL")) {
					// create axis restriction on the LOINC term
					OWLObjectProperty property = createLOINCProperty(LOINCPart
							.getOWLRelationName(relationType));

					OWLClass partClass = createLOINCClass(partID);
					OWLClassExpression expression = factory
							.getOWLObjectSomeValuesFrom(property, partClass);
					subRestrictions.add(expression);
				} else {
					// create axis restriction on the LOINC term
					OWLObjectProperty property = createLOINCProperty(LOINCPart
							.getOWLRelationName(relationType));

					OWLClass partClass = createLOINCClass(partID);
					OWLClassExpression expression = factory
							.getOWLObjectSomeValuesFrom(property, partClass);

					equRestrictions.add(expression);
				}
				// create inverse restriction on the LOINC part
				/*
				 * OWLObjectProperty inverseProperty =
				 * createProperty(LOINCPartBean
				 * .getOWLInverseRelationName(relationType));
				 * 
				 * OWLClassExpression partExpression = factory
				 * .getOWLObjectSomeValuesFrom(inverseProperty, termClass);
				 * 
				 * OWLAxiom axiom = factory.getOWLSubClassOfAxiom(partClass,
				 * partExpression);
				 * 
				 * addAxiom(axiom);
				 * 
				 * // add inverses to properties
				 * addAxiom(factory.getOWLInverseObjectPropertiesAxiom(
				 * property, inverseProperty));
				 */

			}
		}

		// datatype multipart ^^^2
		// TODO:
		if (!index.equals("")) {
			OWLDataProperty property = createLOINCDataProperty("has_index");
			OWLClassExpression expression = factory.getOWLDataHasValue(
					property, factory.getOWLLiteral(Integer.parseInt(index)));
			equRestrictions.add(expression);
		}

		OWLClassExpression definedAxes = factory
				.getOWLObjectIntersectionOf(equRestrictions);

		addAxiom(factory.getOWLEquivalentClassesAxiom(termClass, definedAxes));

		addAxiom(factory.getOWLSubClassOfAxiom(termClass,
				factory.getOWLObjectIntersectionOf(subRestrictions)));

	}

	// FIXME:
	public static void addSnomedEquivalences(
			Map<LOINCPartID, Set<SNOMEDTermID>> loincLinks)
			throws LowlincException {

		// Adding MRCONSO links
		for (Entry<LOINCPartID, Set<SNOMEDTermID>> e : loincLinks.entrySet()) {

			OWLClass partClass = createLOINCClass(e.getKey());

			if (partClass.getSuperClasses(ontology).size() == 0) {
				log.warn(partClass + " has no parents!");
			}
			Set<OWLClassExpression> equivalentSet = new HashSet<>();
			equivalentSet.add(partClass);

			for (SNOMEDTermID snomedID : e.getValue()) {
				if (isObjectProperty(IRI.create(snomedBase.toString()
						+ snomedID.getID()))) {
					log.warn("SNOMED CT ENTITY IS OBJECT PROPERTY " + snomedID);
					continue;
				}
				OWLClass snomedClass = createSnomedClass(snomedID);

				equivalentSet.add(snomedClass);
			}

			OWLAxiom axiom = factory
					.getOWLEquivalentClassesAxiom(equivalentSet);
			addAxiom(axiom);
		}

		addEquivalentRelations();

	}

	public static void addSubclassMapsTo(OWLClass subClass, OWLClass parent)
			throws LowlincException {
		OWLObjectProperty mapsTo = createLOINCPropertyNoObservationDomain("maps_to");
		OWLClassExpression mapsToSomeClass = factory
				.getOWLObjectSomeValuesFrom(mapsTo, parent);
		OWLAxiom axiom = factory.getOWLSubClassOfAxiom(subClass,
				mapsToSomeClass);
		AddAxiom addAxiom = new AddAxiom(ontology, axiom);

		manager.applyChange(addAxiom);
	}

	public static void addTermsParent(OWLClass cls) throws LowlincException {
		OWLClass parent = getTermsParent();
		addLabel(parent, LowlincConfig.getString("TERMS_ROOT_LABEL"));
		addParent(cls, parent);
	}

	private static OWLClass createClass(String base, String accession)
			throws LowlincException {
		if (accession.equals("")) {
			throw new LowlincException("Empty accession");
		}
		OWLClass ent = factory.getOWLClass(IRI.create(base + accession));
		addAxiom(factory.getOWLDeclarationAxiom(ent));
		return ent;
	}

	public static OWLClass createLOINCClass(LOINCID accession)
			throws LowlincException {
		return createClass(documentIRI.toString(), accession.getID());
	}

	private static OWLDataProperty createLOINCDataProperty(String accession) {
		return factory.getOWLDataProperty(IRI.create(documentIRI.toString()
				+ accession));
	}

	private static OWLObjectProperty createLOINCProperty(String accession)
			throws LowlincException {
		OWLObjectProperty prop = createProperty(documentIRI.toString(),
				accession);
		// we set the domain in the observation class
		addAxiom(factory.getOWLObjectPropertyDomainAxiom(prop,
				getObservationsParent()));
		return prop;
	}

	private static OWLObjectProperty createLOINCPropertyNoObservationDomain(
			String accession) {
		return createProperty(documentIRI.toString(), accession);
	}

	// alternative constructor starting with snomed
	// rather than a fresh version of the ontology
	public static void createOntology(Boolean skipSnomed)
			throws LowlincException {
		documentIRI = IRI.create(LowlincConfig.getString("LOINC_IRI"));
		snomedBase = IRI.create(LowlincConfig.getString("SNOMED.base"));

		System.setProperty("entityExpansionLimit", "100000000");
		try {
			if (!skipSnomed) {
				log.info("Loading SNOMED CT");
				loadOntology(LowlincConfig.getString("SNOMEDINOWL.path"));
			} else {
				log.info("Creating empty ontology " + documentIRI.toString());
				ontology = manager.createOntology(documentIRI);
				log.info("Ontology " + documentIRI + " created");
			}
		} catch (OWLOntologyCreationException e) {
			throw new LowlincException(e);
		} catch (java.lang.OutOfMemoryError e) {
			log.fatal("Ran out of memory. Try a bigger heap size, e.g. VM arguments -Xms512M -Xmx512M");
			throw new LowlincException(e.getMessage());
		}
	}

	private static OWLObjectProperty createProperty(String base,
			String accession) {
		return factory.getOWLObjectProperty(IRI.create(base + accession));
	}

	private static OWLClass createSnomedClass(SNOMEDTermID accession)
			throws LowlincException {
		return createClass(snomedBase.toString(), accession.getID());
	}

	private static OWLObjectProperty createSNOMEDProperty(String accession) {
		return createProperty(snomedBase.toString(), accession);
	}

	private static OWLOntologyChange getAddAnnotationChange(OWLEntity cls,
			IRI iriAnnot, String text) {
		// Create the axiom to be added, also first get a type string constant
		OWLAnnotation anno = factory.getOWLAnnotation(
				factory.getOWLAnnotationProperty(iriAnnot),
				factory.getOWLLiteral(text));
		OWLAnnotationAssertionAxiom axiom = factory
				.getOWLAnnotationAssertionAxiom(cls.getIRI(), anno);
		// Link axiom to ontology
		return new AddAxiom(getOntology(), axiom);
	}

	public static OWLClass getDeprecatedParent() {
		return factory.getOWLClass(OWLRDFVocabulary.OWL_DEPRECATED_CLASS
				.getIRI());
	}

	public static String getLabel(OWLEntity ent) throws LowlincException {

		// Firstly, get the annotation property for rdfs:label
		OWLAnnotationProperty label = factory
				.getOWLAnnotationProperty(OWLRDFVocabulary.RDFS_LABEL.getIRI());
		Set<OWLAnnotation> labelSet = ent.getAnnotations(ontology, label);

		if (labelSet.size() > 1) {
			log.warn("Multiple labels on " + ent.toStringID() + labelSet);
		}
		if (labelSet.size() == 0) {
			return ent.toStringID();
		}
		return ((OWLLiteral) labelSet.toArray(new OWLAnnotation[0])[0]
				.getValue()).getLiteral();

	}

	/**
	 * @return the manager
	 */
	public static OWLOntologyManager getManager() {
		return manager;
	}

	private static OWLClass getObservationsParent() throws LowlincException {
		OWLClass parent = createLOINCClass(new LOINCTermID(
				LowlincConfig.getString("OBSERVATION_ROOT_ACCESSION")));
		addLabel(parent, LowlincConfig.getString("OBSERVATION_ROOT_LABEL"));
		return parent;
	}

	/**
	 * Gets the ontology.
	 * 
	 * @return ontology loaded in the constructor
	 */
	public static OWLOntology getOntology() {
		return ontology;
	}

	private static OWLClass getPartsParent() throws LowlincException {
		return createLOINCClass(new LOINCPartID(
				LowlincConfig.getString("PARTS_ROOT_ACCESSION")));
	}

	private static OWLClass getTermsParent() throws LowlincException {
		return createLOINCClass(new LOINCTermID(
				LowlincConfig.getString("TERMS_ROOT_ACCESSION")));
	}

	public static boolean isNewEntity(IRI iri) {
		Set<OWLEntity> set = ontology.getEntitiesInSignature(iri);

		if (set.size() == 0) {
			return true;
		}
		return false;
	}

	private static boolean isObjectProperty(IRI iri) {
		Set<OWLEntity> set = ontology.getEntitiesInSignature(iri);
		if (set.size() > 0) {
			return set.toArray(new OWLEntity[0])[0].isOWLObjectProperty();
		}
		return false;
	}

	public static void loadOntology(String path) throws LowlincException {
		IRI iri = IRI.create(new File(path).toURI());
		try {
			ontology = manager.loadOntologyFromOntologyDocument(iri);
		} catch (OWLOntologyCreationException e) {
			throw new LowlincException(e);
		}
	}

	public static void removeBracketsForFunctionalSyntaxInCondor() {
		log.info("Removing brackets from annotations");
		Pattern p = Pattern.compile("[()]");
		// for all assertion axioms
		// make an internal copy as this is a live collection
		for (OWLAnnotationAssertionAxiom oldAxiom : new HashSet<OWLAnnotationAssertionAxiom>(
				ontology.getAxioms(AxiomType.ANNOTATION_ASSERTION))) {
			OWLLiteral annotLiteral = (OWLLiteral) oldAxiom.getAnnotation()
					.getValue();
			String annotText = annotLiteral.getLiteral();
			// strip brackets
			Matcher m = p.matcher(annotText);
			while (m.find()) {
				annotText = annotText.replace(m.group(), "_");
			}
			// create new axiom
			OWLAnnotation newAnnot = factory.getOWLAnnotation(
					oldAxiom.getProperty(), factory.getOWLLiteral(annotText));
			OWLAnnotationAssertionAxiom newAxiom = factory
					.getOWLAnnotationAssertionAxiom(oldAxiom.getSubject(),
							newAnnot);

			// Replace axioms
			manager.applyChange(new RemoveAxiom(ontology, oldAxiom));
			manager.applyChange(new AddAxiom(ontology, newAxiom));
		}
	}

	public static void saveOntology(String path) throws LowlincException {
		try {
			OWLAnnotation commentAuthor = factory
					.getOWLAnnotation(
							factory.getRDFSComment(),
							factory.getOWLLiteral("Created by Tomasz Adamusiak <tomasz.adamusiak@nih.gov>"));
			manager.applyChange(new AddOntologyAnnotation(ontology,
					commentAuthor));

			log.info("Saving " + path);
			// _manager.saveOntology(_ontologyToSave, new
			// RDFXMLOntologyFormat(), uri);
			manager.saveOntology(ontology, IRI.create(new File(path).toURI()));
		} catch (OWLOntologyStorageException e) {
			throw new LowlincException(e);
		}
	}

	public static void saveOntologyInFunctional(String path)
			throws LowlincException {
		try {
			OWLAnnotation commentAuthor = factory
					.getOWLAnnotation(
							factory.getRDFSComment(),
							factory.getOWLLiteral("Created by Tomasz Adamusiak <tomasz.adamusiak@nih.gov>"));
			manager.applyChange(new AddOntologyAnnotation(ontology,
					commentAuthor));

			removeBracketsForFunctionalSyntaxInCondor();

			log.info("Saving " + path);
			// _manager.saveOntology(_ontologyToSave, new
			// RDFXMLOntologyFormat(), uri);
			manager.saveOntology(ontology,
					new OWLFunctionalSyntaxOntologyFormat(),
					IRI.create(new File(path).toURI()));
		} catch (OWLOntologyStorageException e) {
			throw new LowlincException(e);
		}
	}

	private Ontologies() {
	}

	public void addEquivalence(OWLClass cls1, OWLClass cls2) {
		OWLAxiom axiom = factory.getOWLEquivalentClassesAxiom(cls1, cls2);
		AddAxiom addAxiom = new AddAxiom(ontology, axiom);

		manager.applyChange(addAxiom);
	}

	// Add relation equivs

	private OWLEntity findEntity(String word) throws LowlincException {
		Set<OWLEntity> set = ontology.getEntitiesInSignature(IRI.create(word));

		if (set.size() == 0) {
			throw new LowlincException("Entity " + word + " not found");
		} else if (set.size() > 1) {
			// is that even possible?
			throw new LowlincException("More than one entity found for URI "
					+ word);
		}

		return set.toArray(new OWLEntity[0])[0];
	}

	private OWLClass getSnomedParent() throws LowlincException {
		return createSnomedClass(new SNOMEDTermID(
				LowlincConfig.getString("SNOMED.root")));
	}

	public void removeAnnotations() {
		List<OWLOntologyChange> toRemove = new ArrayList<>();

		for (OWLAnnotationAssertionAxiom annotAxiom : ontology
				.getAxioms(AxiomType.ANNOTATION_ASSERTION)) {
			toRemove.add(new RemoveAxiom(ontology, annotAxiom));
		}

		log.info("Removing " + toRemove.size() + " axioms");
		manager.applyChanges(toRemove);
	}

	public String replaceIDsWithLabels(String inputText)
			throws LowlincException {
		Pattern pattern = Pattern.compile("<(.*?)>");
		Matcher matcher = pattern.matcher(inputText);
		while (matcher.find()) {

			OWLEntity ent = findEntity(matcher.group(1));
			if (ent != null) {
				inputText = inputText.replace(matcher.group(1), getLabel(ent)
						+ " (###" + ent.toStringID() + ")");

			}

		}
		return inputText;
	}

	public static void releaseOntology() {
		manager.removeOntology(ontology);
	}

	public static void addSiblingDisjoints() throws LowlincException {
		for (OWLClassExpression exp : getPartsParent().getSubClasses(ontology)) {
			if (exp.isAnonymous()) {
				continue;
			}
			OWLClass childCls = exp.asOWLClass();
			makeChildSiblingsDisjoint(childCls);
		}

	}

	private static void makeChildSiblingsDisjoint(OWLClass cls) {

		Set<OWLOntology> set = new HashSet<>();
		set.add(ontology);
		OWLCompositeOntologyChange change = new MakePrimitiveSubClassesMutuallyDisjoint(
				factory, cls, set, ontology);

		manager.applyChanges(change.getChanges());

		for (OWLClassExpression exp : cls.getSubClasses(ontology)) {
			if (exp.isAnonymous()) {
				continue;
			}
			OWLClass childCls = exp.asOWLClass();
			makeChildSiblingsDisjoint(childCls);
		}
	}
}
